#include "stdafx.h"
#include <memory.h>
#include "json.h"

// true if character represent a digit
#define IS_DIGIT(c) (c >= '0' && c <= '9')

// convert string to integer
char *atoi(char *first, char *last, int *out)
{
        int sign = 1;
        if (first != last)
        {
                if (*first == '-')
                {
                        sign = -1;
                        ++first;
                }
                else if (*first == '+')
                {
                        ++first;
                }
        }

        int result = 0;
        for (; first != last && IS_DIGIT(*first); ++first)
        {
                result = 10 * result + (*first - '0');
        }
        *out = result * sign;

        return first;
}

// convert hexadecimal string to unsigned integer
char *hatoui(char *first, char *last, unsigned int *out)
{
        unsigned int result = 0;
        for (; first != last; ++first)
        {
                int digit;
                if (IS_DIGIT(*first))
                {
                        digit = *first - '0';
                }
                else if (*first >= 'a' && *first <= 'f')
                {
                        digit = *first - 'a' + 10;
                }
                else if (*first >= 'A' && *first <= 'F')
                {
                        digit = *first - 'A' + 10;
                }
                else
                {
                        break;
                }
                result = 16 * result + digit;
        }
        *out = result;

        return first;
}

// convert string to floating point
char *atof(char *first, char *last, float *out)
{
        // sign
        float sign = 1;
        if (first != last)
        {
                if (*first == '-')
                {
                        sign = -1;
                        ++first;
                }
                else if (*first == '+')
                {
                        ++first;
                }
        }

        // integer part
        float result = 0;
        for (; first != last && IS_DIGIT(*first); ++first)
        {
                result = 10 * result + (*first - '0');
        }

        // fraction part
        if (first != last && *first == '.')
        {
                ++first;

                float inv_base = 0.1f;
                for (; first != last && IS_DIGIT(*first); ++first)
                {
                        result += (*first - '0') * inv_base;
                        inv_base *= 0.1f;
                }
        }

        // result w\o exponent
        result *= sign;

        // exponent
        bool exponent_negative = false;
        int exponent = 0;
        if (first != last && (*first == 'e' || *first == 'E'))
        {
                ++first;

                if (*first == '-')
                {
                        exponent_negative = true;
                        ++first;
                }
                else if (*first == '+')
                {
                        ++first;
                }

                for (; first != last && IS_DIGIT(*first); ++first)
                {
                        exponent = 10 * exponent + (*first - '0');
                }
        }

        if (exponent)
        {
                float power_of_ten = 10;
                for (; exponent > 1; exponent--)
                {
                        power_of_ten *= 10;
                }

                if (exponent_negative)
                {
                        result /= power_of_ten;
                }
                else
                {
                        result *= power_of_ten;
                }
        }

        *out = result;

        return first;
}

json_value *json_alloc(block_allocator *allocator)
{
        json_value *value = (json_value *)allocator->malloc(sizeof(json_value));
        memset(value, 0, sizeof(json_value));
        return value;
}

void json_append(json_value *lhs, json_value *rhs)
{
        rhs->parent = lhs;
        if (lhs->last_child)
        {
                lhs->last_child = lhs->last_child->next_sibling = rhs;
        }
        else
        {
                lhs->first_child = lhs->last_child = rhs;
        }
}

#define ERROR(it, desc)\
        *error_pos = it;\
        *error_desc = (char *)desc;\
        *error_line = 1 - escaped_newlines;\
        for (char *c = it; c != source; --c)\
                if (*c == '\n') ++*error_line;\
        return 0

#define CHECK_TOP() if (!top) {ERROR(it, "Unexpected character");}

json_value *json_parse(char *source, char **error_pos, char **error_desc, int *error_line, block_allocator *allocator)
{
        json_value *root = 0;
        json_value *top = 0;

        char *name = 0;
        char *it = source;

        int escaped_newlines = 0;

        while (*it)
        {
                switch (*it)
                {
                case '{':
                case '[':
                        {
                                // create new value
                                json_value *object = json_alloc(allocator);

                                // name
                                object->name = name;
                                name = 0;

                                // type
                                object->type = (*it == '{') ? JSON_OBJECT : JSON_ARRAY;

                                // skip open character
                                ++it;

                                // set top and root
                                if (top)
                                {
                                        json_append(top, object);
                                }
                                else if (!root)
                                {
                                        root = object;
                                }
                                else
                                {
                                        ERROR(it, "Second root. Only one root allowed");
                                }
                                top = object;
                        }
                        break;

                case '}':
                case ']':
                        {
                                if (!top || top->type != ((*it == '}') ? JSON_OBJECT : JSON_ARRAY))
                                {
                                        ERROR(it, "Mismatch closing brace/bracket");
                                }

                                // skip close character
                                ++it;

                                // set top
                                top = top->parent;
                        }
                        break;

                case ':':
                        if (!top || top->type != JSON_OBJECT)
                        {
                                ERROR(it, "Unexpected character");
                        }
                        ++it;
                        break;

                case ',':
                        CHECK_TOP();
                        ++it;
                        break;

                case '"':
                        {
                                CHECK_TOP();

                                // skip '"' character
                                ++it;

                                char *first = it;
                                char *last = it;
                                while (*it)
                                {
                                        if (*it < '\x20')
                                        {
                                                ERROR(first, "Control characters not allowed in strings");
                                        }
                                        else if (*it == '\\')
                                        {
                                                switch (it[1])
                                                {
                                                case '"':
                                                        *last = '"';
                                                        break;
                                                case '\\':
                                                        *last = '\\';
                                                        break;
                                                case '/':
                                                        *last = '/';
                                                        break;
                                                case 'b':
                                                        *last = '\b';
                                                        break;
                                                case 'f':
                                                        *last = '\f';
                                                        break;
                                                case 'n':
                                                        *last = '\n';
                                                        ++escaped_newlines;
                                                        break;
                                                case 'r':
                                                        *last = '\r';
                                                        break;
                                                case 't':
                                                        *last = '\t';
                                                        break;
                                                case 'u':
                                                        {
                                                                unsigned int codepoint;
                                                                if (hatoui(it + 2, it + 6, &codepoint) != it + 6)
                                                                {
                                                                        ERROR(it, "Bad unicode codepoint");
                                                                }

                                                                if (codepoint <= 0x7F)
                                                                {
                                                                        *last = (char)codepoint;
                                                                }
                                                                else if (codepoint <= 0x7FF)
                                                                {
                                                                        *last++ = (char)(0xC0 | (codepoint >> 6));
                                                                        *last = (char)(0x80 | (codepoint & 0x3F));
                                                                }
                                                                else if (codepoint <= 0xFFFF)
                                                                {
                                                                        *last++ = (char)(0xE0 | (codepoint >> 12));
                                                                        *last++ = (char)(0x80 | ((codepoint >> 6) & 0x3F));
                                                                        *last = (char)(0x80 | (codepoint & 0x3F));
                                                                }
                                                        }
                                                        it += 4;
                                                        break;
                                                default:
                                                        ERROR(first, "Unrecognized escape sequence");
                                                }

                                                ++last;
                                                it += 2;
                                        }
                                        else if (*it == '"')
                                        {
                                                *last = 0;
                                                ++it;
                                                break;
                                        }
                                        else
                                        {
                                                *last++ = *it++;
                                        }
                                }

                                if (!name && top->type == JSON_OBJECT)
                                {
                                        // field name in object
                                        name = first;
                                }
                                else
                                {
                                        // new string value
                                        json_value *object = json_alloc(allocator);

                                        object->name = name;
                                        name = 0;

                                        object->type = JSON_STRING;
                                        object->string_value = first;

                                        json_append(top, object);
                                }
                        }
                        break;

                case 'n':
                case 't':
                case 'f':
                        {
                                CHECK_TOP();

                                // new null/bool value
                                json_value *object = json_alloc(allocator);

                                object->name = name;
                                name = 0;

                                // true
                                if (it[0] == 'n' && it[1] == 'u' && it[2] == 'l' && it[3] == 'l')
                                {
                                        object->type = JSON_NULL;
                                        it += 4;
                                }
                                // false
                                else if (it[0] == 't' && it[1] == 'r' && it[2] == 'u' && it[3] == 'e')
                                {
                                        object->type = JSON_BOOL;
                                        object->int_value = 1;
                                        it += 4;
                                }
                                // null
                                else if (it[0] == 'f' && it[1] == 'a' && it[2] == 'l' && it[3] == 's' && it[4] == 'e')
                                {
                                        object->type = JSON_BOOL;
                                        object->int_value = 0;
                                        it += 5;
                                }
                                else
                                {
                                        ERROR(it, "Unknown identifier");
                                }

                                json_append(top, object);
                        }
                        break;

                case '-':
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                        {
                                CHECK_TOP();

                                // new number value
                                json_value *object = json_alloc(allocator);

                                object->name = name;
                                name = 0;

                                object->type = JSON_INT;

                                char *first = it;
                                while (*it != '\x20' && *it != '\x9' && *it != '\xD' && *it != '\xA' && *it != ',' && *it != ']' && *it != '}')
                                {
                                        if (*it == '.' || *it == 'e' || *it == 'E')
                                        {
                                                object->type = JSON_FLOAT;
                                        }
                                        ++it;
                                }

                                if (object->type == JSON_INT && atoi(first, it, &object->int_value) != it)
                                {
                                        ERROR(first, "Bad integer number");
                                }

                                if (object->type == JSON_FLOAT && atof(first, it, &object->float_value) != it)
                                {
                                        ERROR(first, "Bad float number");
                                }

                                json_append(top, object);
                        }
                        break;

                default:
                        ERROR(it, "Unexpected character");
                }

                // skip white space
                while (*it == '\x20' || *it == '\x9' || *it == '\xD' || *it == '\xA')
                {
                        ++it;
                }
        }

        if (top)
        {
                ERROR(it, "Not all objects/arrays have been properly closed");
        }

        return root;
}
